package org.jflame.commons.json.fastjson2;

import org.jflame.commons.json.JsonCfg;
import org.jflame.commons.json.JsonException;
import org.jflame.commons.json.Jsons;
import org.jflame.commons.reflect.BeanHelper;
import org.jflame.commons.util.StringHelper;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONFactory;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.JSONWriter.Context;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.fastjson2.PropertyNamingStrategy;
import com.alibaba.fastjson2.TypeReference;
import com.alibaba.fastjson2.filter.Labels;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
import com.alibaba.fastjson2.writer.ObjectWriter;

/**
 * 基于fastjson2 实现json工具类
 * 
 * @author charles.zhang
 */
public class Fastjsons2 implements Jsons {

    private Context writeContext;
    private JSONReader.Context readContext;
    // private JsonCfg cfg;

    public Fastjsons2() {
        this(new JsonCfg());
    }

    public Fastjsons2(JsonCfg cfg) {
        configure(cfg);
    }

    @Override
    public String toJson(Object obj) {
        return JSON.toJSONString(obj, writeContext);
    }

    /**
     * Java对象序列化为JSON字符串,按属性组过滤.
     * <p>
     * 使用{@code LabelFilter}定制属性. 如：对象中@JSONField(label = "xx")注解定制.
     * 
     * @param obj Java对象
     * @param includeLabels label分组名称，只包含指定label的属性
     * @return
     */
    public String toJsonView(Object obj, String... includeLabels) {
        try {
            Context cxt = cloneWriteContext();
            cxt.setLabelFilter(Labels.includes(includeLabels));
            return JSON.toJSONString(obj, cxt);
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    @Override
    public String toJsonFilter(Object obj, boolean isInclude, String[] properties) {
        if (isInclude) {
            return toJsonIncludeField(obj, properties);
        } else {
            return toJsonExcludeField(obj, properties);
        }
    }

    /**
     * Java对象序列化为JSON字符串,指定要排除的属性.
     * 
     * @param obj Java对象
     * @param excludeFields 要排除的属性
     * @return
     */
    public String toJsonExcludeField(Object obj, String... excludeFields) {
        SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
        for (String field : excludeFields) {
            filter.getExcludes()
                    .add(field);
        }
        try {
            Context cxt = cloneWriteContext();
            cxt.setPropertyPreFilter(filter);
            return JSON.toJSONString(obj, cxt);
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    /**
     * Java对象序列化为JSON字符串,指定要包含的属性.
     * 
     * @param obj Java对象
     * @param includeFields 要包含的属性
     * @return
     */
    public String toJsonIncludeField(Object obj, String... includeFields) {
        SimplePropertyPreFilter filter = new SimplePropertyPreFilter(includeFields);
        try {
            Context cxt = cloneWriteContext();
            cxt.setPropertyPreFilter(filter);
            return JSON.toJSONString(obj, cxt);
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    @Override
    public byte[] toJsonBytes(Object object) {
        return JSON.toJSONBytes(object, StandardCharsets.UTF_8, writeContext);
    }

    @Override
    public void writeJSONString(OutputStream os, Object object) throws IOException {
        try {
            JSON.writeTo(os, object, writeContext);
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    @Override
    public <T> T parseObject(String jsonStr, Class<T> clazz) {
        try {
            return JSON.parseObject(jsonStr, clazz, readContext);
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    public <T> T parseObject(String jsonStr, TypeReference<T> typeReference) {
        if (StringHelper.isEmpty(jsonStr)) {
            return null;
        }
        JSON.parseObject(jsonStr, readContext);
        return JSON.parseObject(jsonStr, readContext)
                .to(typeReference);
        /*Type type = typeReference.getType();
        boolean fieldBased = (readContext.getFeatures() & JSONReader.Feature.FieldBased.mask) != 0;
        ObjectReader<T> objectReader = readContext.getProvider()
                .getObjectReader(type, fieldBased);
        
        try (JSONReader reader = JSONReader.of(jsonStr, readContext)) {
            T object = objectReader.readObject(reader, type, null, 0);
        
            reader.handleResolveTasks(object);
        
            if (!reader.isEnd() && (readContext.getFeatures() & IgnoreCheckClose.mask) == 0) {
                throw new JSONException(reader.info("input not end"));
            }
            return object;
        }*/
    }

    @Override
    public <T> T parseObject(String jsonStr, Type type) {
        try {
            return JSON.parseObject(jsonStr, type, readContext);
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    @Override
    public <T> T parseObject(InputStream inStream, Charset charset, Class<T> clazz) throws IOException {
        try {
            return JSON.parseObject(inStream, charset, clazz, readContext);
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    public <T> T parseObject(InputStream inStream, Charset charset, Type type) throws IOException {
        try {
            return JSON.parseObject(inStream, charset, type, readContext);
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    @Override
    public <T> List<T> parseList(String jsonStr, Class<T> elementClazz) {
        try {
            JSONArray jsonArray = (JSONArray) JSON.parse(jsonStr, readContext);
            if (jsonArray == null) {
                return null;
            }
            return jsonArray.toJavaList(elementClazz);
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    @Override
    public <K,V> Map<K,V> parseMap(String jsonStr, Class<K> keyClazz, Class<V> valueClazz) {
        try {
            return parseObject(jsonStr, new TypeReference<Map<K,V>>(keyClazz, valueClazz) {
            });
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    @Override
    public Object parseNode(String jsonStr) {
        return JSON.parse(jsonStr, readContext);
    }

    private Context cloneWriteContext() {
        Context context = new Context(writeContext.getProvider());

        // copy filter?
        try {
            BeanHelper.copyProperties(writeContext, context);
        } catch (SecurityException | ReflectiveOperationException e) {
            throw new JsonException(e);
        }
        return context;
    }

    public JSONObject parse(String jsonStr) {
        try {
            return JSON.parseObject(jsonStr, readContext);
        } catch (JSONException e) {
            throw new JsonException(e);
        }
    }

    void setPropertyNamingStrategy(int strategy) {
        switch (strategy) {
            case 1:
                writeContext.getProvider()
                        .setNamingStrategy(PropertyNamingStrategy.CamelCase);
                readContext.getProvider()
                        .setNamingStrategy(PropertyNamingStrategy.CamelCase);
                break;
            case 2:
                writeContext.getProvider()
                        .setNamingStrategy(PropertyNamingStrategy.PascalCase);
                readContext.getProvider()
                        .setNamingStrategy(PropertyNamingStrategy.PascalCase);
                break;
            case 3:
                writeContext.getProvider()
                        .setNamingStrategy(PropertyNamingStrategy.SnakeCase);
                readContext.getProvider()
                        .setNamingStrategy(PropertyNamingStrategy.SnakeCase);
                break;
            case 4:
                writeContext.getProvider()
                        .setNamingStrategy(PropertyNamingStrategy.KebabCase);
                readContext.getProvider()
                        .setNamingStrategy(PropertyNamingStrategy.KebabCase);
                break;
            default:
                break;
        }

    }

    public void configure(JsonCfg cfg) {
        /*if (cfg == null || cfg.equals(this.cfg)) {
            return;
        }
        this.cfg = cfg;*/
        if (writeContext == null) {
            writeContext = JSONFactory.createWriteContext(Feature.IgnoreErrorGetter, Feature.WriteBigDecimalAsPlain);
        }
        if (readContext == null) {
            readContext = JSONFactory.createReadContext(JSONReader.Feature.IgnoreSetNullValue,
                    JSONReader.Feature.IgnoreNullPropertyValue);
        }

        writeContext.config(Feature.WriteBigDecimalAsPlain, cfg.isBigDecimalAsPlain());
        writeContext.config(Feature.PrettyFormat, cfg.isPrettyFormat());
        writeContext.config(Feature.WriteNulls, !cfg.isIgnoreNull());
        writeContext.config(Feature.BrowserCompatible, cfg.getBrowserCompatible());
        writeContext.config(Feature.WriteClassName, cfg.isEnableAutoTypeSupport());
        readContext.config(JSONReader.Feature.SupportAutoType, cfg.isEnableAutoTypeSupport());

        if (StringHelper.isNotEmpty(cfg.getDateFormat())) {
            writeContext.setDateFormat(cfg.getDateFormat());
            // 自定义LocalDate序列化格式
            JSON.register(LocalDate.class, LocalDateWriter.INSTANCE);
        }

        if (cfg.getTimeZone() != null) {
            writeContext.setZoneId(cfg.getTimeZone()
                    .toZoneId());
            readContext.setTimeZone(cfg.getTimeZone());
        }
        if (cfg.getNamingStrategy() != null) {
            setPropertyNamingStrategy(cfg.getNamingStrategy());
        }
    }

    public static class LocalDateWriter implements ObjectWriter<LocalDate> {

        public static final LocalDateWriter INSTANCE = new LocalDateWriter(null);

        private DateTimeFormatter formatter = null;

        public LocalDateWriter(String format) {
            if (StringHelper.isNotEmpty(format)) {
                formatter = DateTimeFormatter.ofPattern(format);
            }
        }

        @Override
        public void write(JSONWriter writer, Object object, Object fieldName, Type fieldType, long features) {
            if (object == null) {
                writer.writeNull();
                return;
            }
            LocalDate localDate = (LocalDate) object;
            if (formatter == null) {
                writer.writeString(localDate.toString());
            } else {
                writer.writeString(localDate.format(formatter));
            }
        }
    }

}
